feat: paid plugin marketplace — entitlement spine, Stripe Connect, FSL policy (0196) - #157
Merged
Merged
Conversation
…icensing Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…okens (0196) New MIT package: signs/verifies/mints offline-verifiable PluginLicense tokens bound to the buyer's DID. Asymmetric (Ed25519) so an adversarial client cannot forge a license — the verify+types ship to the client, the private key stays hub-side. Mirrors the @xnetjs/entitlements sign/verify shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (0196) - XNetExtension gains license / pricing / publisherDid; PluginPricing type + isPaidPricing helper; validateManifest checks the new fields. - MarketplaceEntry mirrors license/pricing/publisherDid for badge + CI policy. - PluginRegistry.install runs a paid-license gate via an injected checkLicense callback (host wires it to @xnetjs/licenses; plugins stays runtime-dep-free). LicenseRequiredError; fail-closed when a priced plugin has no provider. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ath (0196) CheckoutRequest gains an optional `connect` (ConnectCharge): the Stripe adapter routes funds to the seller's connected account via transfer_data[destination] and keeps the platform application fee (application_fee_percent for subs, application_fee_amount for one-time). New connect.ts: applicationFeeMinor / feeBpsToPercent / sellerNetMinor with a 10% (1000 bps) default. This is the 'bring your own Stripe + captured marketplace fee' path (Connect Standard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + CI gate (0196) - license-policy.ts: ALLOWED_PLUGIN_LICENSES (FSL-1.1-MIT default) + pluginLicenseText() generating real FSL/MIT LICENSE files. - scaffolder emits license/pricing/publisherDid into the manifest + a matching LICENSE file. - scripts/check-plugin-licenses.mjs: CI gate that fails a paid listing with a non-approved license; wired into the lint job + 'check:plugin-licenses'. - docs/guides/sell-a-plugin.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mark Phase 0, the @xnetjs/licenses spine + install gate (Phase 1), and the Stripe Connect adapter + fee math (Phase 2 adapter) as shipped; record hub wiring + Phase 3/4 as deferred. Tidy test import order + format the guide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
Preview removed for PR #157. |
…(0196) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| const root = resolve(process.cwd()) | ||
|
|
||
| /** Recursively collect `registry.json` files living under a `marketplace/` dir. */ | ||
| function findRegistries(dir, underMarketplace, out) { |
| const BILLING_KINDS = ['managed', 'byo'] | ||
|
|
||
| /** Validate the optional `pricing` descriptor (exploration 0196). */ | ||
| function validatePricing(pricing: unknown, issues: string[]): void { |
crs48
pushed a commit
that referenced
this pull request
Jun 17, 2026
…caffold test Changelog: none (CI/test hygiene). - apps/web task-filter.test.ts: apply Prettier formatting (CI format-check). - packages/cli connector.test.ts: the scaffolder emits a LICENSE (marketplace requirement, #157) but this expected-file list was stale and red on main already — align it with plugin.test.ts, which already expects LICENSE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
crs48
added a commit
that referenced
this pull request
Jun 18, 2026
Backfills the notable user-facing PRs that merged before the changelog automation existed (or were flagged `needs-changelog`): | Entry | PR | |---|---| | A richer, interactive changelog | #151 | | xNet adapts to your phone (mobile workbench) | #156 | | See your local agent in the AI panel | #163 | | Paid plugins | #157 | | Agent-native Connectors | #160 | | A new look — cosmic-web app icons | #153 | Each fragment carries its **original** PR number, so `resolve-prs` leaves it untouched and the gallery/link point at the right PR. The **#156 entry auto-pulls its real mobile-vs-desktop before/after sliders** from `visuals/pr/156/` — confirming the auto-gallery works on a backfilled entry. This PR also exercises the new flow itself: it **adds fragments**, so the required `changelog-section` check passes via the fragment-present path (no `skip-changelog` label). Verified locally: `validate:changelog` → 19 fragments valid; `astro build` clean; all 6 articles + the #156 before/after gallery render. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48
added a commit
that referenced
this pull request
Jun 18, 2026
## What Backfills the changelog so it reflects what's live on `main`. The per-PR fragment system (exploration 0197) was current through PR #163 (PR #178 backfilled #151/#153/#156/#157/#160/#163), but the recent **0198/0199 batch** and a couple of stragglers shipped without entries. This adds 7 fragments, with prose lifted from each PR's own `## Changelog` block where present. | PR | Entry | Tags | |---|---|---| | #143 | AI chat that knows your workspace | `ai` | | #167 | Linear-grade Tasks | `tasks` `app` | | #169 | Cleaner page editor typography | `editor` | | #170 | Bring your Slack data into xNet | `plugins` `platform` | | #171 | Smoother, more consistent motion | `app` | | #173 | Airtable-grade database grids | `app` | | #174 | A redesigned chat & channels experience | `app` | ## Deliberately excluded - **In-editor AI (#154 / #158)** — shipped the transform engine + diff/approval logic, but both PRs state the `/ai` UI wiring into the running app is **deferred**, so it isn't user-visible yet. An entry would overclaim. - **Slack (#170)** — entry scoped to the migration connector + compatibility layer (genuinely live); avoids claiming the webhook/slash-command hub endpoints work, since they're generic-over-an-injected-sink and not mounted in a running server yet. - Pure infra/ops/docs PRs (cloud M1/M2, CI, runbooks) — not user-facing. ## Verification - `validate-changelog.ts` → **26 fragments valid** (19 existing + 7 new) - `prettier --check` on all new files → clean - Newest-first ordering confirmed — the 7 entries slot in at the top ## Changelog <!-- This PR only adds changelog fragments for already-merged work; it ships no user-facing change of its own. --> 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the foundation of the paid plugin marketplace from exploration 0196: pay plugin authors a one-time or monthly fee, "bring your own Stripe" and capture a small marketplace fee, and ship source-available plugins that auto-open after 2 years.
The key reframing
"Use your own Stripe" and "capture an xNet fee" are the same product via Stripe Connect Standard: the author keeps their own dashboard/payouts/KYC, and xNet attaches a 10%
application_fee. There is no way to skim a fee from a standalone account — Connect is the supported version of that wish.What ships here (all tested, MIT)
Phase 0 — paid-aware catalog + FSL policy
XNetExtensiongainslicense/pricing/publisherDid; newPluginPricingtype +isPaidPricing+ manifest validation.MarketplaceEntrymirrors them.license-policy.ts:ALLOWED_PLUGIN_LICENSES(default FSL-1.1-MIT) +pluginLicenseText()generating real FSL/MITLICENSEfiles; the scaffolder emits them.scripts/check-plugin-licenses.mjs: CI gate that fails a paid listing with a non-approved license (wired into thelintjob). Proven against good/bad fixtures.docs/guides/sell-a-plugin.md.Phase 1 — entitlement spine
@xnetjs/licensespackage: Ed25519, DID-boundPluginLicensetokens —sign/verify/mint/checkLicenseFor, expiry + grace,kidfor rotation. Asymmetric on purpose (the client is the adversary; it can't forge).PluginRegistry.installruns a fail-closed paid-license gate via an injectedcheckLicensecallback —@xnetjs/pluginsstays runtime-dependency-free of the verifier.Phase 2 — adapter half
@xnetjs/billing:CheckoutRequest.connectroutes funds to the seller's connected account (transfer_data[destination]) and keeps the platformapplication_fee(percent for subs, amount for one-time).connect.tsfee math with a 10% (1000 bps) default.Deferred (documented in the exploration's checklists)
Hub wiring that needs live Stripe / larger integration: seller-onboarding Connect OAuth,
POST /marketplace/checkout, webhook→license-mint + refresh, payouts dashboard, the BYO-billing path (Phase 3), and lifecycle/revocation/2-year auto-open (Phase 4).Validation
@xnetjs/licenses(16 tests), billing connect+stripe (16), plugins gates+manifest+scaffold+license-policy (40) — all green; full plugins suite 499 pass.typecheckgreen for licenses/billing/plugins;check:cloud-boundary+check:plugin-licensesgreen; prettier clean on changed TS.pnpm install+ committed lockfile.🤖 Generated with Claude Code